package com.lf.mykotlin.coroutines

import android.util.Log
import kotlinx.coroutines.*


/**
 * @date: 2023/5/4
 */
class MyCoroutines {

    /*
        协程

           1.  线程和协程的区别
                线程：
                    1. 线程是操作系统级别的概念
                    2. 我们开发者通过编程语言(Thread.java)创建的线程，本质还是操作系统内核线程的映射
                    3. JVM 中的线程与内核线程的存在映射关系，有“一对一”，“一对多”，“M对N”。JVM 在不同操作系统中的具体
                        实现会有差别，“一对一”是主流
                    4. 一般情况下，我们说的线程，都是内核线程，线程之间的切换，调度，都由操作系统负责
                    5. 线程也会消耗操作系统资源，但比进程轻量得多
                    6. 线程，是抢占式的，它们之间能共享内存资源，进程不行
                    7. 线程共享资源导致了 多线程同步问题
                    8. 有的编程语言会自己实现一套线程库，从而能在一个内核线程中实现多线程效果，早期 JVM 的“绿色线程”
                        就是这么做的，这种线程被称为“用户线程”

                协程：
                    1. Kotlin 协程，不是操作系统级别的概念，无需操作系统支持
                    2. Kotlin 协程，有点像上面提到的“绿色线程”，一个线程上可以运行成千上万个协程
                    3. Kotlin 协程，是用户态的(userlevel)，内核对协程无感知
                    4. Kotlin 协程，是协作式的，由开发者管理，不需要操作系统进行调度和切换，也没有抢占式的消耗，因此它更加高效
                    5. Kotlin 协程，它底层基于状态机实现，多协程之间共用一个实例，资源开销极小，因此它更加轻量
                    6. Kotlin 协程，本质还是运行于线程之上，它通过协程调度器，可以运行到不同的线程上

           2. 协程和线程的关系
                1. 从包含关系上看，协程跟线程的关系，有点像“线程与进程的关系”，毕竟，协程不可能脱离线程运行。
                2. 协程虽然不能脱离线程而运行，但可以在不同的线程之间切换
                3. Kotlin 协程的核心竞争力在于：它能简化异步并发任务。


           3. 协程中的元素
                Coroutine scope 协程的作用域
                Job 任务，封装了协程中需要执行的代码逻辑。Job 可以取消并且有简单生命周期
                Coroutine context 协程上下文，协程上下文里是各种元素的集合
                Coroutine dispatchers 协程调度，可以指定协程运行在 Android 的哪个线程里
                suspend 挂起函数，挂起，就是一个稍后会被自动切回来的线程调度操作

     */


    /*
        1. 三种方式启动携程
     */
    fun test1() {

        // 方法一，使用 runBlocking 顶层函数
        // 通常适用于单元测试的场景，而业务开发中不会用到这种方法，因为它是线程阻塞的
        runBlocking {

        }

        // 指定调度器，在IO线程中执行
        runBlocking(Dispatchers.IO) {
            // 这里是协程的作用域
            Log.d("liduo", "123")
        }

        // 方法二，使用 GlobalScope 单例对象
        // 非阻塞式 但是它的生命周期会和 app 一致，且不能取消
        val job = GlobalScope.launch {

        }

        job.start()

        // 方法三，自行通过 CoroutineContext 创建一个 CoroutineScope 对象
        // 我们可以通过 context 参数去管理和控制协程的生命周期
        val coroutineScope = CoroutineScope(Dispatchers.IO)
        val job2 = coroutineScope.launch {

        }
        job2.start()


    }

    // async 启动协程
    // async方法与launch方法的不同之处在于可以携带返回值
    private suspend fun test(): Int {
        // 作用域为GlobalScope，返回值为Int类型，，泛型可省略，自动推断
        val deffer = GlobalScope.async<Int> {
            Log.d("liduo", "123")
            // 延时1s
            delay(1000)
            1
        }
        // 获取返回值
        return deffer.await()
    }


    /*
        2. 协程是如何保证线程安全的
                Dispatchers.Main 调用程序在Android的主线程中
                Dispatchers.IO 适合主线程之外的执行磁盘或者网络io操作，例如文件的读取与写入，任何的网络请求
                Dispatcher.Default 适合主线程之外的，cpu的操作，例如json数据的解析，以及列表的排序，

     */
    fun test2() {

        // 切换到IO线程执行
        CoroutineScope(Dispatchers.IO).launch {

        }

        // 切换到主线程执行
        CoroutineScope(Dispatchers.Main).launch {
            // your code
            withContext(Dispatchers.IO) {
                // 阻塞和耗时代码
            }

            // your code
            withContext(Dispatchers.IO) {
                // 阻塞和耗时代码
            }

            // your code

        }
    }

    /*
        3. Suspend 函数
              1. 需要耗时操作的时候
              2. 给函数前加上suspend 关键字，把内容用withContext包起来
              3. 作用： 提醒调用者我是需要耗时操作，需要用挂起的方式,在协程中使用
              4. 挂起函数只能在挂起函数或者协程作用域中使用

         协程的挂起本质： 本质就是切线程，完成之后只不过可以自动切回来
               协程挂起就是切个线程，在挂起函数执行完毕之后，协程会自动的重新切回它原先的线程，也就是稍后会被切回来的线程切换。
               切回来就是resume，恢复功能是协程，所以suspend函数需要在另一个suspend函数或者协程中调用

     */
    suspend fun testSuspend() {
        withContext(Dispatchers.IO) {
            // 耗时操作
        }
    }

    /*
        协程的创建和启动

     */
    fun testCreate() {

        //创建一个协程
        val scope = CoroutineScope(Dispatchers.Main+Job())

        // 通过Job获取协程的生命周期
        scope.launch{

        }

        // 其他耗时请求，例如从数据库中获取数据
        val deferred = scope.async {

        }
    }


    /*
        协程的取消
     */
    fun testCancle() {
        val scope = CoroutineScope(Dispatchers.Main+ Job())
        scope.launch {
            val job = launch {
                val job1 =  launch {

                }
            }
            job.cancel()
        }
        scope.cancel()
    }

}